﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace ImageRecognition2
{
    /// <summary>
    /// This is Thrown if a list couldn't be copied correctly
    /// </summary>
    public class CCustomDictionaryCloneException : Exception
    {
        private object m_oObjectValue = null;
        private object m_oObjectKey = null;

        /// <summary>
        /// Gets the object value.
        /// </summary>
        /// <value>The object value.</value>
        public object ObjectValue
        {
            get { return this.m_oObjectValue; }
        }

        /// <summary>
        /// Gets the object key.
        /// </summary>
        /// <value>The object key.</value>
        public object ObjectKey
        {
            get { return this.m_oObjectKey; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CCustomDictionaryCloneException"/> class.
        /// </summary>
        public CCustomDictionaryCloneException()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CCustomDictionaryCloneException"/> class.
        /// </summary>
        /// <param name="_oKey">The _o key.</param>
        /// <param name="_oValue">The _o value.</param>
        public CCustomDictionaryCloneException(object _oKey, object _oValue)
        {
            this.m_oObjectKey = _oKey;
            this.m_oObjectValue = _oValue;
        }
    }

    /// <summary>
    /// Use this class to handle collections with several objects be added to the same key...
    /// this list is a common class.
    /// so the key has to be of the type K
    /// and the value has to be of the type T
    /// internally this is handled as a
    /// Dictionary with a generic list of type T as a value-Type
    /// </summary>
    /// <typeparam name="K"></typeparam>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public class CCustomDictionary<K, T> : Dictionary<K, List<T>>, ICloneable
    {
        private bool m_bThrowCloneModeException = true;

        public bool ThrowCloneModeException
        {
            get { return this.m_bThrowCloneModeException; }
            set { this.m_bThrowCloneModeException = value; }
        }


        /// <summary>
        /// Adds the specified _k.
        /// </summary>
        /// <param name="_k">The _k.</param>
        /// <param name="_t">The _t.</param>
        public virtual void Add(K _k, T _t)
        {
            if (this.ContainsKey(_k))
            {
                List<T> list = this[_k];
                list.Add(_t);
                this[_k] = list;
            }
            else
            {
                List<T> list = new List<T>();
                list.Add(_t);
                base.Add(_k, list);
            }
        }

        /// <summary>
        /// Test if object is contained at current key position...
        /// </summary>
        /// <param name="_k"></param>
        /// <param name="_t"></param>
        /// <returns></returns>
        public virtual bool ContainsObject(K _k, T _t)
        {
            if (this.ContainsKey(_k))
            {
                return ((List<T>)this[_k]).Contains(_t);
            }
            return false;
        }
        /// <summary>
        /// Removes the specified _k.
        /// </summary>
        /// <param name="_k">The _k.</param>
        /// <param name="_nIndex">Index of the _n.</param>
        public virtual void Remove(K _k, int _nIndex)
        {
            List<T> list = this[_k];
            list.RemoveAt(_nIndex);
            this[_k] = list;
        }

        /// <summary>
        /// Removes the specified _k.
        /// </summary>
        /// <param name="_k">The _k.</param>
        /// <param name="_nRangeStart">The _n range start.</param>
        /// <param name="_nRangeEnd">The _n range end.</param>
        public virtual void Remove(K _k, int _nRangeStart, int _nRangeEnd)
        {
            List<T> list = this[_k];
            list.RemoveRange(_nRangeStart, _nRangeEnd - _nRangeStart);
            this[_k] = list;
        }

        /// <summary>
        /// Gets the <see cref="T"/> with the specified _k.
        /// </summary>
        /// <value></value>
        public T this[K _k, int _nIndex]
        {
            get
            {
                List<T> list = this[_k];
                return list[_nIndex];
            }
        }

        /// <summary>
        /// Get list of given elements from specified index...
        /// </summary>
        /// <param name="_k"></param>
        /// <param name="_nStartIndex"></param>
        /// <param name="_nCount"></param>
        /// <returns></returns>
        public virtual List<T> this[K _k, int _nStartIndex, int _nCount]
        {
            get { return ((List<T>)this[_k]).GetRange(_nStartIndex, _nCount); }
        }

        /// <summary>
        /// gets the object count of a given key.
        /// </summary>
        /// <param name="_k">The _k.</param>
        /// <returns></returns>
        public virtual int ObjectCount(K _k)
        {
            return ((List<T>)this[_k]).Count;
        }

        /// <summary>
        /// Bestimmt, ob das angegebene <see cref="T:System.Object"></see> und das aktuelle <see cref="T:System.Object"></see> gleich sind.
        /// </summary>
        /// <param name="obj">Das <see cref="T:System.Object"></see>, das mit dem aktuellen <see cref="T:System.Object"></see> verglichen werden soll.</param>
        /// <returns>
        /// true, wenn das angegebene <see cref="T:System.Object"></see> gleich dem aktuellen <see cref="T:System.Object"></see> ist, andernfalls false.
        /// </returns>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// Fungiert als Hashfunktion für einen bestimmten Typ. <see cref="M:System.Object.GetHashCode"></see> eignet sich für die Verwendung in Hashalgorithmen und Hashdatenstrukturen, z. B. in einer Hashtabelle.
        /// </summary>
        /// <returns>
        /// Ein Hashcode für das aktuelle <see cref="T:System.Object"></see>.
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Implementiert die <see cref="T:System.Runtime.Serialization.ISerializable"></see>-Schnittstelle und gibt die zum Serialisieren der <see cref="T:System.Collections.Generic.Dictionary`2"></see>-Instanz erforderlichen Daten zurück.
        /// </summary>
        /// <param name="info">Ein <see cref="T:System.Runtime.Serialization.SerializationInfo"></see>-Objekt mit den zum Serialisieren der <see cref="T:System.Collections.Generic.Dictionary`2"></see>-Instanz erforderlichen Informationen.</param>
        /// <param name="context">Eine <see cref="T:System.Runtime.Serialization.StreamingContext"></see>-Struktur, die die Quelle und das Ziel des serialisierten Streams enthält, der der <see cref="T:System.Collections.Generic.Dictionary`2"></see>-Instanz zugeordnet ist.</param>
        /// <exception cref="T:System.ArgumentNullException">info ist null.</exception>
        public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            base.GetObjectData(info, context);
        }

        /// <summary>
        /// Implementiert die <see cref="T:System.Runtime.Serialization.ISerializable"></see>-Schnittstelle und löst das Deserialisierungsereignis aus, sobald die Deserialisierung abgeschlossen ist.
        /// </summary>
        /// <param name="sender">Die Quelle des Deserialisierungsereignisses.</param>
        /// <exception cref="T:System.Runtime.Serialization.SerializationException">Das der aktuellen <see cref="T:System.Collections.Generic.Dictionary`2"></see>-Instanz zugeordnete <see cref="T:System.Runtime.Serialization.SerializationInfo"></see>-Objekt ist ungültig.</exception>
        public override void OnDeserialization(object sender)
        {
            base.OnDeserialization(sender);
        }

        /// <summary>
        /// Gibt einen <see cref="T:System.String"></see> zurück, der den aktuellen <see cref="T:System.Object"></see> darstellt.
        /// </summary>
        /// <returns>
        /// Ein <see cref="T:System.String"></see>, der den aktuellen <see cref="T:System.Object"></see> darstellt.
        /// </returns>
        public override string ToString()
        {
            return base.ToString();
        }

        #region ICloneable Member

        /// <summary>
        /// Erstellt ein neues Objekt, das eine Kopie der aktuellen Instanz darstellt.
        /// </summary>
        /// <returns>
        /// Ein neues Objekt, das eine Kopie dieser Instanz darstellt.
        /// </returns>
        public virtual object Clone()
        {
            CCustomDictionary<K, T> aDict = (CCustomDictionary<K, T>)this.MemberwiseClone();
            IEnumerator enumerator = this.GetEnumerator();
            while (enumerator.MoveNext())
            {
                KeyValuePair<K, List<T>> keyValuePair = (KeyValuePair<K, List<T>>)enumerator.Current;
                List<T> list = (List<T>)keyValuePair.Value;
                List<T> listCloned = new List<T>();
                foreach (object oObjectToClone in list)
                {
                    if (oObjectToClone is ICloneable)
                    {
                        listCloned.Add((T)(((ICloneable)oObjectToClone).Clone()));
                    }
                }
                //check for each entry if cloned correctly
                if (this.m_bThrowCloneModeException)
                {
                    if (listCloned.Count != list.Count)
                    {
                        throw new CCustomDictionaryCloneException(keyValuePair.Key, keyValuePair.Value);
                    }
                }
                foreach (object oClonedElement in listCloned)
                {
                    aDict.Add((K)((ICloneable)keyValuePair.Key).Clone(), (T)oClonedElement);
                }
                if (this.m_bThrowCloneModeException)
                {
                    //check if entry count match (old list <> new list)
                    if (aDict.Keys.Count != this.Keys.Count)
                    {
                        throw new CCustomDictionaryCloneException(keyValuePair.Key, keyValuePair.Value);
                    }
                }
            }
            return aDict;
        }
        #endregion
    }
}
